#include "optimize.h"

typedef struct unit{
    Operand op1,op2;
    int op;
}unit;

typedef union {
    uLL ptr;
    struct{
        uLL int1 : 32;
        uLL int2 : 27;
        int int1_is_const : 1;
        int swap_ord : 1;
        int op : 3;
    }num;
}util;
static uLL encode(unit x){
    Operand op1=x.op1,op2=x.op2;
    util ret;
    ret.ptr=0;
    int op=x.op-OP_ASSIGN;
    if(x.op==OP_DIV&&op2.var.no==0&&op2.var.addr_status==CONSTANT_ID) return 0;
    if(op2.var.addr_status==CONSTANT_ID) {
        Operand op=op1;op1=op2;op2=op;
        if(x.op!=OP_ADD&&x.op!=OP_MUL) ret.num.swap_ord=1;
    }
    Assert(op2.var.addr_status!=CONSTANT_ID,"should have benn optimized!");
    Assert(op>=0&&op<=4,"Unexpected op type %d",x.op);
    ret.num.int1=op1.var.no;
    ret.num.int2=op?op2.var.no:0;
    ret.num.int1_is_const=(op1.var.addr_status==CONSTANT_ID);
    ret.num.op=op;
    return ret.ptr;
}
static bool find(uLL ptr, int var){
    util x;x.ptr=ptr;
    if((x.num.int1==var&&!x.num.int1_is_const)||(x.num.int2==var)) return true;
    return false;
}

bool common_merge(MAP x,MAP y){
    if(!map_find(x,0)){
        map_cpy(y,x);
        return true;
    }
    list nxt=NULL;
    bool ret=false;
    FOR_MAP_2(iter,x,nxt){
        nxt=iter->nxt;
        uLL key=MAP_KEY(iter);
        if(key==0) continue;
        void * ptr=MAP_VAL(iter,void *);
        void ** temp=map_find(y,key);
        if(!temp||*temp!=ptr) map_erase(x,key),ret=true;
    }
    return ret;
}

static inline Operand get_operand(MAP self,Operand x){
    if(x.var.addr_status!=0) return x;
    if(var_mark[x.var.no]) return (Operand){};
    uLL ptr=encode((unit){.op1=x,.op2=(Operand){},.op=OP_ASSIGN});
    void ** temp=map_find(self,ptr);
    if(temp) return (Operand){.var.no=(uLL)*temp,.var.addr_status=0};
    return x;
}

void common_statement(MAP x,operation * o){
    int op=o->op;
    uLL ptr=0;
    if(op>=OP_ADD&&op<=OP_DIV) {
        Operand xx=o->x,y=o->y,z=o->z;
        if(y.var.addr_status==0||y.var.addr_status==CONSTANT_ID){
            if(z.var.addr_status==0||z.var.addr_status==CONSTANT_ID){
                if(xx.var.addr_status==0){
                    if(var_mark[xx.var.no])return;
                    y=get_operand(x,y);z=get_operand(x,z);
                    if((y.var.addr_status==0&&y.var.no==0)||(z.var.addr_status==0&&z.var.no==0)) return; 
                    ptr=encode((unit){.op1=y,.op2=z,.op=op});
                    if(ptr&&!find(ptr,xx.var.no)){
                        void ** temp=map_insert(x,ptr);
                        if(!*temp) *temp=(void *)((uLL)xx.var.no);
                        else {
                            ptr=encode((unit){.op1=xx,.op2={},.op=OP_ASSIGN});
                            *map_insert(x,ptr)=*temp;
                        }
                    } 
                }
            }
        }
    }if(op==OP_ASSIGN){
        Operand xx=o->x,y=o->y;
        if(xx.var.addr_status==0&&y.var.addr_status==0){
            y=get_operand(x,y);
            if(var_mark[xx.var.no]||(y.var.addr_status==0&&y.var.no==0)) return;
            ptr=encode((unit){.op1=xx,.op2={},.op=OP_ASSIGN});
            *map_insert(x,ptr)=(void *)((uLL)y.var.no);
        }
    }
    if(op>=OP_CALL&&op<=OP_DIV&&o->x.var.addr_status==0){
        Operand xx=o->x;
        if(var_mark[xx.var.no]) return;
        list nxt=NULL;
        FOR_MAP_2(iter,x,nxt){
            nxt=iter->nxt;
            uLL key=MAP_KEY(iter);
            if(key==ptr) continue;
            if(MAP_VAL(iter,uLL)==xx.var.no||find(key,xx.var.no)) map_erase(x,key);
        }    
    }
    return;
}

bool common_optimize(MAP x,operation * o){
    int op=o->op;
    if(op<OP_ADD||op>OP_DIV) return true;
    Operand y=o->y,z=o->z;
    if(y.var.addr_status!=0&&y.var.addr_status!=CONSTANT_ID) return true;
    if(z.var.addr_status!=0&&z.var.addr_status!=CONSTANT_ID) return true;
    y=get_operand(x,y);z=get_operand(x,z);
    if((y.var.addr_status==0&&y.var.no==0)||(z.var.addr_status==0&&z.var.no==0)) return true;
    uLL ptr=encode((unit){.op1=y,.op2=z,.op=op});
    if(!ptr) return true;
    void ** temp=map_find(x,ptr);
    if(temp) {
        o->op=OP_ASSIGN;
        o->y.var.no=(uLL)(*temp);
        o->y.var.addr_status=0;
        o->z.var.no=o->z.var.addr_status=0;
    }
    return true;
}

MAP common_boundary_init(List x){
    MAP ret=new_map();
    *map_insert(ret,0)=NULL;
    return ret;
}